package tv.dyndns.kishibe.server;

import java.sql.SQLException;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.eclipse.jetty.websocket.WebSocket;

import tv.dyndns.kishibe.client.packet.PacketServerStatus;
import tv.dyndns.kishibe.client.packet.PacketUserData;
import tv.dyndns.kishibe.server.database.Database;
import tv.dyndns.kishibe.server.websocket.BroadcastWebSockets;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.inject.Inject;

public class ServerStatusManager {
	private static final int UPDATE_DURATION = 10; // 秒
	private final Database database;
	@VisibleForTesting
	final AtomicInteger numberOfPageView = new AtomicInteger();
	private final AtomicInteger numberOfTotalSessions = new AtomicInteger();
	private final AtomicInteger numberOfTotalPlayers = new AtomicInteger();
	private final AtomicInteger numberOfCurrentSessions = new AtomicInteger();
	private final AtomicInteger numberOfCurrentPlayers = new AtomicInteger();
	@VisibleForTesting
	volatile Set<Integer> loginUserCodes = Collections.synchronizedSet(new HashSet<Integer>());
	private volatile List<PacketUserData> loginUsers = Lists.newArrayList();
	private final GameManager gameManager;
	private final NormalModeProblemManager normalModeProblemManager;
	private final PlayerHistoryManager playerHistoryManager;
	private final BroadcastWebSockets<PacketServerStatus> serverStatusWebSockets;
	private volatile PacketServerStatus serverStatus;

	@Inject
	public ServerStatusManager(Database database, GameManager gameManager,
			NormalModeProblemManager normalModeProblemManager,
			PlayerHistoryManager playerHistoryManager,
			BroadcastWebSockets<PacketServerStatus> serverStatusWebSockets) {
		this.database = database;
		this.gameManager = gameManager;
		this.normalModeProblemManager = normalModeProblemManager;
		this.playerHistoryManager = playerHistoryManager;
		this.serverStatusWebSockets = serverStatusWebSockets;

		ThreadPool.get().addMinuteTasks(new Runnable() {
			public void run() {
				saveServerStatus();
			}
		});

		ThreadPool.get().addMinuteTasks(new Runnable() {
			public void run() {
				updateLoginUsers();
			}
		});

		ThreadPool.get().scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				try {
					updateServerStatus();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}, UPDATE_DURATION, UPDATE_DURATION, TimeUnit.SECONDS);
		try {
			updateServerStatus();
		} catch (SQLException e) {
			e.printStackTrace();
		}

		// データベースから読み込み
		PageView pageView;
		try {
			pageView = database.loadPageView();
		} catch (SQLException e) {
			e.printStackTrace();
			return;
		}
		numberOfPageView.set(pageView.numberOfPageView);
		numberOfTotalSessions.set(pageView.numberOfSessions);
		numberOfTotalPlayers.set(pageView.numberOfPlayers);
	}

	public void login() {
		numberOfPageView.getAndIncrement();
	}

	@VisibleForTesting
	void updateServerStatus() throws SQLException {
		PacketServerStatus status = new PacketServerStatus();
		status.numberOfCurrentSessions = numberOfCurrentSessions.get();
		status.numberOfTotalSessions = numberOfTotalSessions.get();
		status.numberOfCurrentPlayers = numberOfCurrentPlayers.get();
		status.numberOfTotalPlayers = numberOfTotalPlayers.get();
		status.numberOfProblems = normalModeProblemManager.getNumberOfProblem();
		status.numberOfPageView = numberOfPageView.get();
		// 読み取りのみなので同期不要
		status.numberOfLoginPlayers = Math.max(loginUsers.size(), loginUserCodes.size());
		status.numberOfActivePlayers = database.getNumberOfActiveUsers();
		status.numberOfPlayersInWhole = gameManager.getNumberOfPlayersInWhole();
		status.limitedRemoteAddresses = database.getLimitedRemoteAddresses();
		status.limitedUserCodes = database.getLimitedUserCodes();
		status.lastestPlayers = playerHistoryManager.get();
		if (Objects.equal(serverStatus, status)) {
			return;
		}
		serverStatus = status;
		serverStatusWebSockets.send(status);
	}

	public PacketServerStatus getServerStatus() {
		return serverStatus;
	}

	public void saveServerStatus() {
		PageView pageView = new PageView();
		pageView.numberOfPageView = numberOfPageView.get();
		pageView.numberOfPlayers = numberOfTotalPlayers.get();
		pageView.numberOfSessions = numberOfTotalSessions.get();
		try {
			database.savePageView(pageView);
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	public void keepAlive(int userCode) {
		loginUserCodes.add(userCode);
	}

	public List<PacketUserData> getLoginUsers() {
		return loginUsers;
	}

	private void updateLoginUsers() {
		Set<Integer> userCodes = loginUserCodes;
		loginUserCodes = Collections.synchronizedSet(new HashSet<Integer>());

		List<PacketUserData> list = Lists.newArrayList();
		for (int userCode : userCodes) {
			try {
				list.add(database.getUserDataByUserCode(userCode));
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}

		loginUsers = list;
	}

	public void changeStatics(int sessionDelta, int playDelta) {
		numberOfCurrentSessions.set(gameManager.getNumberOfSessions());
		numberOfCurrentPlayers.set(gameManager.getNumberOfPlayers());
		numberOfTotalSessions.addAndGet(sessionDelta);
		numberOfTotalPlayers.addAndGet(playDelta);
	}

	public WebSocket getServerStatusWebSocket() {
		return serverStatusWebSockets.newWebSocket();
	}
}
